Android  사용자 정의 잠 금 화면 그림 보기

머리말
Android 사용자 정의 View 기능 은 고급 엔지니어 가 되 는 데 필수 적 인 것 입 니 다.필 자 는 사용자 정의 View 는 지름길 이 없고 자주 연습 해 야 제품 수 요 를 해결 할 수 있다 고 생각 합 니 다.필자 도 오랫동안 사용자 정의 View 를 쓰 지 않 았 으 니 빨리 컨트롤 을 써 서 느낌 을 찾 아 보 자.
본 고 는 잠 금 화면 도안 의 사용자 정의 컨트롤 을 실현 한다.효과 도 는 다음 과 같다.
Github 주소:AndroidSample

LockView 소개
사용자 정의 속성:

인용 방법:
(1)레이아웃 파일 에 도입

<com.xing.androidsample.view.LockView
  android:id="@+id/lock_view"
  app:rowCount="4"
  app:normalColor=""
  app:moveColor=""
  app:errorColor=""
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:layout_margin="40dp" />
(2)코드 에 정확 한 그림 을 설정 하여 일치 하 는 지 확인 하고 리 셋 에서 결 과 를 얻 을 수 있 습 니 다.

List<Integer> intList = new ArrayList<>();
  intList.add(3);
  intList.add(7);
  intList.add(4);
  intList.add(2);
  lockView.setStandard(intList);
  lockView.setOnDrawCompleteListener(new LockView.OnDrawCompleteListener() {
   @Override
   public void onComplete(boolean isSuccess) {
    Toast.makeText(CustomViewActivity.this, isSuccess ? "success" : "fail", Toast.LENGTH_SHORT).show();
   }
  });
사고의 방향 을 실현 하 다.
  • 기본 상태 로 rowCount*rowCount 원 을 그립 니 다.겉 원 색상 은 안쪽 원 색상 에 투명 도 를 더 해 야 합 니 다
  • onTouchEvent()방법 에서 현재 터치 점 과 각 원 의 원심 거리 가 원 의 반지름 보다 작은 지 판단 하고 각 원 이 이때 어느 상태(normal,move,error)에 있 는 지 결정 하 며 invalidate()를 호출 하여 색 을 다시 그립 니 다
  • 손가락 이 미끄러져 만 진 원 의 좌 표를 Array List 에 추가 하고 Path 로 이 집합 에서 선택 한 원 을 연결 하면 그 어 진 경로 선 을 그 릴 수 있 습 니 다
  • 실현 절차
    사용자 정의 속성
    res/values 디 렉 터 리 에 새 attrs.xml 파일 을 만 듭 니 다:
    
    <?xml version="1.0" encoding="utf-8"?>
    <resources>
     <declare-styleable name="LockView">
      <attr name="normalColor" format="color|reference" /> <!--     -->
      <attr name="moveColor" format="color|reference" />  <!--         --> 
      <attr name="errorColor" format="color|reference" />  <!--         --> 
      <attr name="rowCount" format="integer" />    <!--        --> 
     </declare-styleable>
    </resources>
    사용자 정의 속성 가 져 오기
    
    public LockView(Context context) {
       this(context, null);
      }
    
      public LockView(Context context, @Nullable AttributeSet attrs) {
       super(context, attrs);
       readAttrs(context, attrs);
       init();
      }
    
     /**
     *        
     */
      private void readAttrs(Context context, AttributeSet attrs) {
       TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.LockView);
       normalColor = typedArray.getColor(R.styleable.LockView_normalColor, DEFAULT_NORMAL_COLOR);
       moveColor = typedArray.getColor(R.styleable.LockView_moveColor, DEFAULT_MOVE_COLOR);
       errorColor = typedArray.getColor(R.styleable.LockView_errorColor, DEFAULT_ERROR_COLOR);
       rowCount = typedArray.getInteger(R.styleable.LockView_rowCount, DEFAULT_ROW_COUNT);
       typedArray.recycle();
      }
    
     /**
     *    
     */
      private void init() {
       stateSparseArray = new SparseIntArray(rowCount * rowCount);
       points = new PointF[rowCount * rowCount];
    
       innerCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
       innerCirclePaint.setStyle(Paint.Style.FILL);
    
       outerCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
       outerCirclePaint.setStyle(Paint.Style.FILL);
    
       linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
       linePaint.setStyle(Paint.Style.STROKE);
       linePaint.setStrokeCap(Paint.Cap.ROUND);
       linePaint.setStrokeJoin(Paint.Join.ROUND);
       linePaint.setStrokeWidth(30);
       linePaint.setColor(moveColor);
      }
    원 의 반지름 을 계산 하 다
    외원 반경 과 인접 한 두 원 사이 의 간격 을 설정 하고 내원 반경 은 외원 반경의 절반 이기 때문에 반경 계산 방식 은 다음 과 같다.
    
    radius = Math.min(w, h) / (2 * rowCount + rowCount - 1) * 1.0f;
    각 원 좌표 설정
    각 원 좌 표 는 1 차원 배열 로 저장 하고 계산 방식 은 다음 과 같다.
    
    //         
    for (int i = 0; i < rowCount * rowCount; i++) {
      points[i] = new PointF(0, 0);
      points[i].set((i % rowCount * 3 + 1) * radius, (i / rowCount * 3 + 1) * radius);
    }
    측정 View 너비
    측정 모드 에 따라 컨트롤 의 너비 와 높이 를 설정 합 니 다.레이아웃 파일 에 wrap 을 설정 합 니 다.content,기본적으로 컨트롤 폭 을 600 dp 로 설정 합 니 다.
    
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
      super.onMeasure(widthMeasureSpec, heightMeasureSpec);
      int width = getSize(widthMeasureSpec);
      int height = getSize(heightMeasureSpec);
      setMeasuredDimension(width, height);
     }
    
     private int getSize(int measureSpec) {
      int mode = MeasureSpec.getMode(measureSpec);
      int size = MeasureSpec.getSize(measureSpec);
      if (mode == MeasureSpec.EXACTLY) {
       return size;
      } else if (mode == MeasureSpec.AT_MOST) {
       return Math.min(size, dp2Px(600));
      }
      return dp2Px(600);
     }
    onTouchEvent()터치 이벤트
    손가락 이 미 끄 러 지 는 과정 에서 현재 터치 점 좌표 가 원 의 범위 에 떨 어 졌 는 지 에 따라 원 의 상 태 를 업데이트 하고 다시 그 릴 때 새로운 색 으로 그립 니 다.손가락 을 들 어 올 릴 때 저 장 된 상태의 list 를 원 의 list 를 선택 하고 linePath 를 초기 화하 고 결 과 를 되 돌려 줍 니 다.
     
    
    private PointF touchPoint;
    
     @Override
     public boolean onTouchEvent(MotionEvent event) {
      switch (event.getAction()) {
       case MotionEvent.ACTION_DOWN:
        reset();
       case MotionEvent.ACTION_MOVE:
        if (touchPoint == null) {
         touchPoint = new PointF(event.getX(), event.getY());
        } else {
         touchPoint.set(event.getX(), event.getY());
        }
        for (int i = 0; i < rowCount * rowCount; i++) {
         //           
         if (getDistance(touchPoint, points[i]) < radius) {
          stateSparseArray.put(i, STATE_MOVE);
          if (!selectedList.contains(points[i])) {
           selectedList.add(points[i]);
          }
          break;
         }
        }
        break;
       case MotionEvent.ACTION_UP:
        if (check()) { //     
         if (listener != null) {
          listener.onComplete(true);
         }
         for (int i = 0; i < stateSparseArray.size(); i++) {
          int index = stateSparseArray.keyAt(i);
          stateSparseArray.put(index, STATE_MOVE);
         }
        } else {  //     
         for (int i = 0; i < stateSparseArray.size(); i++) {
          int index = stateSparseArray.keyAt(i);
          stateSparseArray.put(index, STATE_ERROR);
         }
         linePaint.setColor(0xeeff0000);
         if (listener != null) {
          listener.onComplete(false);
         }
        }
        touchPoint = null;
        if (timer == null) {
         timer = new Timer();
        }
        timer.schedule(new TimerTask() {
         @Override
         public void run() {
          linePath.reset();
          linePaint.setColor(0xee0000ff);
          selectedList.clear();
          stateSparseArray.clear();
          postInvalidate();
         }
        }, 1000);
        break;
      }
      invalidate();
      return true;
     }
    각 원 과 각 원 사이 의 연결 선 을 그립 니 다.
    
    @Override
     protected void onDraw(Canvas canvas) {
      super.onDraw(canvas);
      drawCircle(canvas);
      drawLinePath(canvas);
     }
    
     private void drawCircle(Canvas canvas) {
      //       0     8,          
      for (int index = 0; index < rowCount * rowCount; index++) {
       int state = stateSparseArray.get(index);
       switch (state) {
        case STATE_NORMAL:
         innerCirclePaint.setColor(normalColor);
         outerCirclePaint.setColor(normalColor & 0x66ffffff);
         break;
        case STATE_MOVE:
         innerCirclePaint.setColor(moveColor);
         outerCirclePaint.setColor(moveColor & 0x66ffffff);
         break;
        case STATE_ERROR:
         innerCirclePaint.setColor(errorColor);
         outerCirclePaint.setColor(errorColor & 0x66ffffff);
         break;
       }
       canvas.drawCircle(points[index].x, points[index].y, radius, outerCirclePaint);
       canvas.drawCircle(points[index].x, points[index].y, radius / 2f, innerCirclePaint);
      }
     }
    전체 보기 코드:
    
    /**
     * Created by star.tao on 2018/5/30.
     * email: [email protected]
     * github: https://github.com/xing16
     */
    
    public class LockView extends View {
    
     private static final int DEFAULT_NORMAL_COLOR = 0xee776666;
     private static final int DEFAULT_MOVE_COLOR = 0xee0000ff;
     private static final int DEFAULT_ERROR_COLOR = 0xeeff0000;
     private static final int DEFAULT_ROW_COUNT = 3;
    
     private static final int STATE_NORMAL = 0;
     private static final int STATE_MOVE = 1;
     private static final int STATE_ERROR = 2;
    
    
     private int normalColor; //        
     private int moveColor; //       
     private int errorColor; //     
    
     private float radius; //     
    
     private int rowCount;
    
     private PointF[] points; //               
    
     private Paint innerCirclePaint; //     
    
     private Paint outerCirclePaint; //     
    
     private SparseIntArray stateSparseArray;
    
     private List<PointF> selectedList = new ArrayList<>();
    
     private List<Integer> standardPointsIndexList = new ArrayList<>();
    
     private Path linePath = new Path(); //        
    
     private Paint linePaint;
    
     private Timer timer;
    
     public LockView(Context context) {
      this(context, null);
     }
    
     public LockView(Context context, @Nullable AttributeSet attrs) {
      super(context, attrs);
      readAttrs(context, attrs);
      init();
     }
    
    
     private void readAttrs(Context context, AttributeSet attrs) {
      TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.LockView);
      normalColor = typedArray.getColor(R.styleable.LockView_normalColor, DEFAULT_NORMAL_COLOR);
      moveColor = typedArray.getColor(R.styleable.LockView_moveColor, DEFAULT_MOVE_COLOR);
      errorColor = typedArray.getColor(R.styleable.LockView_errorColor, DEFAULT_ERROR_COLOR);
      rowCount = typedArray.getInteger(R.styleable.LockView_rowCount, DEFAULT_ROW_COUNT);
      typedArray.recycle();
     }
    
     private void init() {
      stateSparseArray = new SparseIntArray(rowCount * rowCount);
      points = new PointF[rowCount * rowCount];
    
      innerCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
      innerCirclePaint.setStyle(Paint.Style.FILL);
    
      outerCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
      outerCirclePaint.setStyle(Paint.Style.FILL);
    
    
      linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
      linePaint.setStyle(Paint.Style.STROKE);
      linePaint.setStrokeCap(Paint.Cap.ROUND);
      linePaint.setStrokeJoin(Paint.Join.ROUND);
      linePaint.setStrokeWidth(30);
      linePaint.setColor(moveColor);
    
     }
    
    
     @Override
     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
      super.onSizeChanged(w, h, oldw, oldh);
      //      =          = 2     
      radius = Math.min(w, h) / (2 * rowCount + rowCount - 1) * 1.0f;
      //         
      for (int i = 0; i < rowCount * rowCount; i++) {
       points[i] = new PointF(0, 0);
       points[i].set((i % rowCount * 3 + 1) * radius, (i / rowCount * 3 + 1) * radius);
      }
     }
    
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
      super.onMeasure(widthMeasureSpec, heightMeasureSpec);
      int width = getSize(widthMeasureSpec);
      int height = getSize(heightMeasureSpec);
      setMeasuredDimension(width, height);
     }
    
     private int getSize(int measureSpec) {
      int mode = MeasureSpec.getMode(measureSpec);
      int size = MeasureSpec.getSize(measureSpec);
      if (mode == MeasureSpec.EXACTLY) {
       return size;
      } else if (mode == MeasureSpec.AT_MOST) {
       return Math.min(size, dp2Px(600));
      }
      return dp2Px(600);
     }
    
    
     @Override
     protected void onDraw(Canvas canvas) {
      super.onDraw(canvas);
      drawCircle(canvas);
      drawLinePath(canvas);
     }
    
     private void drawCircle(Canvas canvas) {
      //       0     8,          
      for (int index = 0; index < rowCount * rowCount; index++) {
       int state = stateSparseArray.get(index);
       switch (state) {
        case STATE_NORMAL:
         innerCirclePaint.setColor(normalColor);
         outerCirclePaint.setColor(normalColor & 0x66ffffff);
         break;
        case STATE_MOVE:
         innerCirclePaint.setColor(moveColor);
         outerCirclePaint.setColor(moveColor & 0x66ffffff);
         break;
        case STATE_ERROR:
         innerCirclePaint.setColor(errorColor);
         outerCirclePaint.setColor(errorColor & 0x66ffffff);
         break;
       }
       canvas.drawCircle(points[index].x, points[index].y, radius, outerCirclePaint);
       canvas.drawCircle(points[index].x, points[index].y, radius / 2f, innerCirclePaint);
      }
     }
    
     /**
      *             
      *
      * @param canvas
      */
     private void drawLinePath(Canvas canvas) {
      //   linePath
      linePath.reset();
      //         0  ,       
      if (selectedList.size() > 0) {
       //           
       linePath.moveTo(selectedList.get(0).x, selectedList.get(0).y);
       for (int i = 1; i < selectedList.size(); i++) {
        linePath.lineTo(selectedList.get(i).x, selectedList.get(i).y);
       }
       //      ,touchPoint   null,           ,   ,
       if (touchPoint != null) {
        linePath.lineTo(touchPoint.x, touchPoint.y);
       }
       canvas.drawPath(linePath, linePaint);
      }
     }
    
     private PointF touchPoint;
    
     @Override
     public boolean onTouchEvent(MotionEvent event) {
      switch (event.getAction()) {
       case MotionEvent.ACTION_DOWN:
        reset();
       case MotionEvent.ACTION_MOVE:
        if (touchPoint == null) {
         touchPoint = new PointF(event.getX(), event.getY());
        } else {
         touchPoint.set(event.getX(), event.getY());
        }
        for (int i = 0; i < rowCount * rowCount; i++) {
         //           
         if (getDistance(touchPoint, points[i]) < radius) {
          stateSparseArray.put(i, STATE_MOVE);
          if (!selectedList.contains(points[i])) {
           selectedList.add(points[i]);
          }
          break;
         }
        }
        break;
       case MotionEvent.ACTION_UP:
        if (check()) { //     
         if (listener != null) {
          listener.onComplete(true);
         }
         for (int i = 0; i < stateSparseArray.size(); i++) {
          int index = stateSparseArray.keyAt(i);
          stateSparseArray.put(index, STATE_MOVE);
         }
        } else {  //     
         for (int i = 0; i < stateSparseArray.size(); i++) {
          int index = stateSparseArray.keyAt(i);
          stateSparseArray.put(index, STATE_ERROR);
         }
         linePaint.setColor(0xeeff0000);
         if (listener != null) {
          listener.onComplete(false);
         }
        }
        touchPoint = null;
        if (timer == null) {
         timer = new Timer();
        }
        timer.schedule(new TimerTask() {
         @Override
         public void run() {
          linePath.reset();
          linePaint.setColor(0xee0000ff);
          selectedList.clear();
          stateSparseArray.clear();
          postInvalidate();
         }
        }, 1000);
        break;
      }
      invalidate();
      return true;
     }
    
     /**
      *          ,    invalidate()       
      */
     private void reset() {
      touchPoint = null;
      linePath.reset();
      linePaint.setColor(0xee0000ff);
      selectedList.clear();
      stateSparseArray.clear();
     }
    
    
     public void onStop() {
      timer.cancel();
     }
    
     private boolean check() {
      if (selectedList.size() != standardPointsIndexList.size()) {
       return false;
      }
      for (int i = 0; i < standardPointsIndexList.size(); i++) {
       Integer index = standardPointsIndexList.get(i);
       if (points[index] != selectedList.get(i)) {
        return false;
       }
      }
      return true;
     }
    
     public void setStandard(List<Integer> pointsList) {
      if (pointsList == null) {
       throw new IllegalArgumentException("standard points index can't null");
      }
      if (pointsList.size() > rowCount * rowCount) {
       throw new IllegalArgumentException("standard points index list can't large to rowcount * columncount");
      }
      standardPointsIndexList = pointsList;
     }
    
     private OnDrawCompleteListener listener;
    
     public void setOnDrawCompleteListener(OnDrawCompleteListener listener) {
      this.listener = listener;
     }
    
    
     public interface OnDrawCompleteListener {
      void onComplete(boolean isSuccess);
     }
    
    
     private float getDistance(PointF centerPoint, PointF downPoint) {
      return (float) Math.sqrt(Math.pow(centerPoint.x - downPoint.x, 2) + Math.pow(centerPoint.y - downPoint.y, 2));
    
     }
    
     private int dp2Px(int dpValue) {
      return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue, getResources().getDisplayMetrics());
     }
    
    }
    이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.

    좋은 웹페이지 즐겨찾기